package cn.mapway.tools.rpc;

import cn.mapway.plugin.rpc.RpcCodeContext;
import cn.mapway.tools.doc.model.Entry;
import cn.mapway.tools.doc.tools.ParameterTools;
import cn.mapway.tools.rpc.model.ApiEntry;
import cn.mapway.tools.rpc.model.ExportContext;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.ArrayInitializerExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.nutz.lang.util.Regex;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;


/**
 * GwtRpcGen
 *
 * @author zhangjianshe@gmail.com
 */
@Slf4j
@Data
public class GwtRpcGen {
    private final RpcCodeContext context;

    public GwtRpcGen(RpcCodeContext context) {
        this.context = context;
    }

    /**
     * 文件是否在 包中
     *
     * @param f
     * @param packages
     * @return
     */
    private static boolean fileInPackages(File f, List<String> packages) {
        String name = Files.getName(f);

        String filePath = f.getAbsolutePath();
        int index = filePath.lastIndexOf(name);
        if (index >= 0) {
            filePath = filePath.substring(0, index);
        }
        if (filePath.endsWith("/") || filePath.endsWith("\\")) {
            filePath = filePath.substring(0, filePath.length() - 1);
        }
        filePath = filePath.replaceAll("[\\\\/]+", ".");
        for (String packageName : packages) {
            if (filePath.endsWith(packageName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 1.分析 @Controller 或者 @RestController
     * 2.过滤其地址 excludes 为空，就输出 includes里的接口,includes 为空,则输出所有的,
     * 注意 includes 和 excludes 可以为正则表达式
     * 3.对每一个接口输出一个方法
     * 3.1 为方法创建输入参数 和输出参数
     */
    public void run() {
        CombinedTypeSolver typeSolver = new CombinedTypeSolver();
        typeSolver.add(new ReflectionTypeSolver());
        context.getParseLocations().stream().forEach(l -> {
            log.info("添加解析路径{}",l);
            typeSolver.add(new JavaParserTypeSolver(new File(l)));
        });

        context.getParseLibLocations().stream().forEach(l -> {
            List<File> files = listAllFiles(l, "jar");

            files.stream().forEach(
                    file -> {
                        JarTypeSolver typeSolver1 = null;
                        try {
                            log.info("添加解析Jar文件{}",file.getAbsolutePath());
                            typeSolver1 = new JarTypeSolver(new File(file.getAbsolutePath()));
                        } catch (IOException e) {
                            log.warn("添加LIB路径{}", e.getMessage());
                        }
                        typeSolver.add(typeSolver1);
                    }
            );
        });

        JavaSymbolSolver javaSymbolSolver = new JavaSymbolSolver(typeSolver);
        StaticJavaParser.getConfiguration().setSymbolResolver(javaSymbolSolver);

        List<String> locations = context.getParseLocations();
        if (Lang.isEmpty(locations)) {
            log.warn("没有配置扫描的路径");
            return;
        }
        List<String> packages = context.getParsePackages();
        if (Lang.isEmpty(packages)) {
            log.warn("没有配置扫描的包名称");
            return;
        }


        List<ApiEntry> entryList = new ArrayList<>();
        //获取所有的要分析的Java文件
        final List<File> allFiles = new ArrayList<>();
        locations.stream().forEach(l -> {
            //寻找所有的路径
            List<File> files = listAllFiles(l, "java");
            allFiles.addAll(files);
        });


        //过滤所有满足 索要扫描的包中包含的文件

        List<File> tempFiles = allFiles.stream().filter(f -> {
            return fileInPackages(f, packages);
        }).collect(Collectors.toList());

        List<String> filePaths = tempFiles.stream().map(f -> f.getAbsolutePath()).collect(Collectors.toList());

        log.info("即将分析" + filePaths.size() + "个下列文件\r\n");
        log.info("搜索文件列表\r{}", Json.toJson(filePaths, JsonFormat.forLook()));

        log.info("不会输出下列路径的接口:\r\n{}", Json.toJson(context.getExcludes()));
        log.info("输出下列路径的接口:\r\n{}", Json.toJson(context.getIncludes()));

        for (File f : tempFiles) {
            List<ApiEntry> entries1 = parseFile(f);
            if (Lang.isNotEmpty(entries1)) {
                entryList.addAll(entries1);
            }
        }
        writeEntrys(entryList);
    }

    /**
     * 输出接口文件，准备通过maven进行打包
     *
     * @param entryList
     */
    private void writeEntrys(List<ApiEntry> entryList) {

        entryList.stream().forEach(entry->{
            System.out.println(entry.getName()+"=====>"+entry.getUrl());
            entry.getReturnParameter().toString();
        });

    }

    /**
     * 列出所有文件
     *
     * @param filePath 文件路径
     * @return {@link List<File>}
     */
    private List<File> listAllFiles(String filePath, String suffix) {
        File[] dirs = Files.scanDirs(new File(filePath));
        ArrayList list = new ArrayList<File>();
        for (File d : dirs) {
            File[] lsFile = Files.files(d, suffix);
            if (lsFile != null) {
                for (int i = 0; i < lsFile.length; i++) {
                    list.add(lsFile[i]);
                }
            }
        }
        return list;
    }

    /**
     * 解析文件
     *
     * @param file 文件
     * @return data
     */
    private List<ApiEntry> parseFile(File file) {
        try {
            CompilationUnit unit = StaticJavaParser.parse(file);
            NodeList<TypeDeclaration<?>> types = unit.getTypes();
            for (int i = 0; i < types.size(); i++) {
                //文件中定义的类
                TypeDeclaration<?> typeDeclaration = types.get(i);
                Optional<AnnotationExpr> controller = typeDeclaration.getAnnotationByClass(Controller.class);
                Optional<AnnotationExpr> restController = typeDeclaration.getAnnotationByClass(RestController.class);

                if (controller.isPresent()) {
                    return processController(false, typeDeclaration);
                } else if (restController.isPresent()) {
                    return processController(true, typeDeclaration);
                } else {
                    log.warn("{} is not a controller", typeDeclaration.getName());
                    return new ArrayList<>();
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
        return new ArrayList<>();
    }


    /**
     * 过程控制器
     *
     * @param typeDeclaration 类型声明
     * @return {@link List<Entry>}
     */
    private List<ApiEntry> processController(boolean restController, TypeDeclaration<?> typeDeclaration) {
        log.info("处理控制器 {}", typeDeclaration.getName());
        Optional<AnnotationExpr> requestMapping = typeDeclaration.getAnnotationByClass(RequestMapping.class);
        return processApi(restController, typeDeclaration, requestMapping);
    }

    /**
     * 处理API
     *
     * @param restController
     * @param typeDeclaration
     * @param requestMapping
     * @return
     */
    private List<ApiEntry> processApi(final boolean restController, TypeDeclaration<?> typeDeclaration, Optional<AnnotationExpr> requestMapping) {

        String leadPath = "";
        if (requestMapping.isPresent()) {
            List<String> paths = annoKeyListValue(requestMapping.get(), "","value", "path");
            //这里假定用户只输入了一个路径信息,大多数应该都只会设置一个路径信息
            leadPath = paths.size() > 0 ? paths.get(0) : "";
        }
        log.info("URL parser {}  {} ",typeDeclaration.getNameAsString(),leadPath);
        String finalLeadPath = leadPath;
        List<ApiEntry> entries = typeDeclaration.getMethods().stream().filter(md ->
                isApi(md)
        ).map(md -> parseEntry(restController, finalLeadPath, md)).filter(e -> e.getIsValid()).collect(Collectors.toList());
        return entries;
    }

    /**
     * 是api
     *
     * @param md 医学博士
     * @return {@link Boolean}
     */
    private Boolean isApi(MethodDeclaration md) {
        if (!md.isPublic()) {
            return false;
        }
        Optional<AnnotationExpr> req = md.getAnnotationByClass(RequestMapping.class);
        Optional<AnnotationExpr> get = md.getAnnotationByClass(GetMapping.class);
        Optional<AnnotationExpr> post = md.getAnnotationByClass(PostMapping.class);

        return req.isPresent() || get.isPresent() || post.isPresent();
    }

    /**
     * 过程的url
     *
     * @param md    医学博士
     * @param entry 条目
     * @param post  帖子
     */
    private void processUrl(MethodDeclaration md, String leadPath, ApiEntry entry, Optional<AnnotationExpr> post, List<String> methodNames) {
        post.ifPresent(postMapping -> {
            List<String> paths = annoKeyListValue(postMapping, "value", "path");
            if (Lang.isNotEmpty(methodNames)) {
                entry.getMethods().addAll(methodNames);
            }
            entry.setName(md.getNameAsString());
            if (paths.size() > 0) {
                entry.setUrl(concatPath(leadPath, paths.get(0)));
            } else {
                log.warn("{} is not config path", md.toString());
                entry.setIsValid(false);
            }
            // we has got the url ,now we are ready to compare whether it can be accpted.
            if (Lang.isNotEmpty(context.getExcludes())) {
                // project has defined excludes so we check where the url matches the excludes
                for (String exclude : context.getExcludes()) {
                    if (Regex.match(exclude, entry.getUrl())) {
                        log.warn("用户配置 忽略路径{}", entry.getUrl());
                        entry.setIsValid(false);
                        return;
                    }
                }
            }
            if (Lang.isNotEmpty(context.getIncludes())) {
                //project has defined the include regx ,so we check it;
                for (String include : context.getIncludes()) {
                    if (Regex.match(include, entry.getUrl())) {
                        entry.setIsValid(true);
                        return;
                    }
                }
                log.warn("url {} 不再 include配置中", entry.getUrl());
                entry.setIsValid(false);
            } else {
                entry.setIsValid(true);
            }
        });
    }

    /**
     * concat路径
     *
     * @param path0 path0
     * @param path1 path1
     * @return {@link String}
     */
    private String concatPath(String path0, String path1) {
        if (Strings.isBlank(path0)) {
            return path1;
        }
        String temp = Strings.trim(path0);
        if (!temp.endsWith("/")) {
            temp = temp + "/";
        }
        String temp2 = Strings.trim(path1);
        while (temp2.startsWith("/")) {
            temp2 = temp2.substring(1);
        }
        return temp + temp2;
    }

    /**
     * 庵野键列表值
     *
     * @param anno 伊斯兰教纪元
     * @param keys 键
     * @return {@link List<String>}
     */
    private List<String> annoKeyListValue(AnnotationExpr anno, String... keys) {
        if (anno == null || Lang.isEmpty(keys)) {
            return new ArrayList<>();
        }
        List<Node> childNodes = anno.getChildNodes();
        final List<String> values = new ArrayList<>();
        for (Node n : childNodes) {
            if (n instanceof MemberValuePair) {
                MemberValuePair pair = (MemberValuePair) n;
                String key = pair.getNameAsString();
                if (Lang.contains(Lang.array(keys), key)) {
                    Expression exp = pair.getValue();
                    exp.ifStringLiteralExpr(t -> {
                        values.add(t.asString());
                    });
                    exp.ifArrayInitializerExpr(t -> extractList(t, values));
                    exp.ifFieldAccessExpr(t -> values.add(t.getNameAsString()));
                    log.info(exp.getClass().getName());
                }
            }
        }
        return values;
    }

    /**
     * 提取列表
     *
     * @param t      t
     * @param values 值
     */
    private void extractList(ArrayInitializerExpr t, List<String> values) {
        NodeList<Expression> values1 = t.getValues();
        for (int i = 0; i < values1.size(); i++) {
            Expression expression = values1.get(i);
            expression.ifStringLiteralExpr(t1 -> values.add(t1.asString()));
        }
    }

    /**
     * 解析条目
     *
     * @param md 医学博士
     * @return {@link Entry}
     */
    private ApiEntry parseEntry(boolean isRestController, String leadPath, MethodDeclaration md) {
        final ApiEntry entry = new ApiEntry();

        //处理Path
        Optional<AnnotationExpr> req = md.getAnnotationByClass(RequestMapping.class);
        Optional<AnnotationExpr> get = md.getAnnotationByClass(GetMapping.class);
        Optional<AnnotationExpr> post = md.getAnnotationByClass(PostMapping.class);
        //三种注解 只会走到期中一种注解
        processUrl(md, leadPath, entry, get, Lang.list("GET"));
        processUrl(md, leadPath, entry, post, Lang.list("POST"));

        if (req.isPresent()) {
            AnnotationExpr temp = req.get();
            List<String> methods = annoKeyListValue(temp, "method");
            if (methods.size() == 0) {
                processUrl(md, leadPath, entry, req, Lang.list("GET"));
            } else {
                processUrl(md, leadPath, entry, req, methods);
            }
        }


        if (!entry.getIsValid()) { //这个API 不合格，直接返回 不处理参数了
            return entry;
        }


        //处理输出参数
        ParameterTools.processOutputParameter(entry, md);

        //处理输入参数
        ParameterTools.processInputParameter(entry, md);

        //处理QueryForm参数

        //处理路径参数

        return entry;
    }
}
